/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package axil.compiler.ast;

import axil.compiler.Compilation;
import axil.compiler.Scope;
import axil.compiler.lexer.Identifier;
import axil.definition.ConstantDefinition;
import axil.vm.opcode.*;

import static axil.framework.Functions.*;


/**
 * An AST node defining an identifier. The identifier can be any of a variety
 * of different kinds.
 */
public class IdentifierNode extends AbstractNode {
	private static final int undefined = -1;

	private enum Kind {
		scoped_simple,		// A value existing only for a lexical scope
		scope_compound,		// A compound identifier from a scoped value
		external_simple,	// A simple host application object identifier
		external_compound,	// A compound host application object identifier
		constant			// A constant in scope by inclusion of a module
	}

	private String ident;
	private int id;
	private Kind kind;
	private ConstantDefinition value;


	public IdentifierNode(Identifier ident) {
		super(ident);
		this.ident = ident.identity();
		this.id = undefined;
	}


	/**
	 * Get the identifier defined by this node. If this node does not define an
	 * identifier, then an exception is thrown.
	 */
	public String identity() {
		return ident;
	}


	public int id() {
		if (id == undefined) {
			throw abort("The identifier does not have an assigned ID.",
			            nv("ident", this));
		}
		return id;
	}


	public void id(int id) {
		assert id != undefined;
		if (this.id != undefined) {
			throw abort("The identifier already has an assigned ID.",
			            nv("ident", this));
		}
		this.id = id;
	}


	/**
	 * Provide a brief description of what this node is. This should include the
	 * kind of thing it is, not the contents of the thing.
	 */
	public String what() {
		return "identifier";
	}


	/**
	 * Dump the content of this node to the diagnostic device. This is strictly
	 * for internal diagnostics, not for application use.
	 */
	public void dump(int indent) {
		debug(indent, "IDENT " + quote(ident));
	}


	/**
	 * Compile this node, verifying correctness and performing any optimizations
	 * possible. The compilation context and scope given are never null but may
	 * not contain the desired functions and identifiers.
	 */
	public Node compile(Compilation context, Scope scope) {
		if (id == undefined) {
			/*
			 * There is no assigned ID, which means we need to determine what it
			 * is.
			 */
			String name = ident;
			int i = ident.indexOf('.');
			if (i > 0) {
				name = ident.substring(0, i);
				if (context.external(name)) {
					kind = Kind.external_compound;
				} else {
					/*
					 * At this point, we have an identifier in dot notation and
					 * it is not something seen in the context. So, it must be
					 * a local variable.
					 */
					IdentifierNode node = scope.identifier(name);
					if (node == null) {
						throw problem(this, "undefined-identifier",
						              nv("identifier", ident));
					}
					kind = Kind.scope_compound;
					id = node.id();
				}
			} else {
				if (context.external(name)) {
					kind = Kind.external_simple;
				} else {
					value = context.constant(ident);
					if (value == null) {
						IdentifierNode node = scope.identifier(name);
						if (node == null) {
							throw problem(this, "undefined-identifier",
							              nv("identifier", ident));
						}
						kind = Kind.scoped_simple;
						id = node.id();
					} else {
						kind = Kind.constant;
					}
				}
			}
		} else {
			/*
			 * There is an assigned ID. so, the assigned ID was from a explicit
			 * point of declaration. This means it can only be a scoped simple
			 * value, such as a variable or function argument.
			 */
			kind = Kind.scoped_simple;
		}
        /*
         * Only externally scoped values are included in the list of references.
         * If locally referenced information is needed, the use() function is available.
         */
        if ((kind == Kind.external_simple) || (kind == Kind.external_compound)) {
            context.references(ident);
        }
        return this;
	}


	/**
	* Emit VM instructions that represents an executable version of this AST
	* node. The instruction returned cannot be null. An exception is thrown if
	* any latent defects in the AST are discovered.
	*/
	public Opcode emit() {
		switch(kind) {
		case scoped_simple:
			return new OpStackVar(this, ident, id);

		case scope_compound:
			return new OpStackPath(this, id, ident);

		case external_simple:
			return new OpContextSimple(this, ident);

		case external_compound:
			return new OpContextPath(this, ident);

		case constant:
			return new OpLiteral(this, value.value());
		}
		throw unreachable();
	}


	/**
	 * Return a string representation of this object. The string returned is
	 * never null.
	 */
	public String toString() {
		return kind.toString() + ' ' + quote(ident);
	}
}
